package ae.pcfc.etks.online.itworx.ehs.web.web.controller;

import java.io.IOException;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.MappingJsonFactory;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.propertyeditors.CustomCollectionEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;

import ae.pcfc.etks.online.common.delegate.UserDelegate;
import ae.pcfc.etks.online.common.vo.UserDetailVO;
import ae.pcfc.etks.online.itworx.ehs.web.common.exception.helper.ExceptionHandlerHelper;
import ae.pcfc.etks.online.itworx.ehs.web.constants.GlobalConstants;
import ae.pcfc.etks.online.itworx.ehs.web.delegate.FNPDelegate;
import ae.pcfc.etks.online.itworx.ehs.web.delegate.WorkflowDelegate;
import ae.pcfc.etks.online.itworx.ehs.web.entity.OFCRequest;
import ae.pcfc.etks.online.itworx.ehs.web.exception.CommunicationException;
import ae.pcfc.etks.online.itworx.ehs.web.exception.FNPServiceExpcetion;
import ae.pcfc.etks.online.itworx.ehs.web.exception.InvalidInputException;
import ae.pcfc.etks.online.itworx.ehs.web.model.FNP;
import ae.pcfc.etks.online.itworx.ehs.web.model.FNPFeesParameter;
import ae.pcfc.etks.online.itworx.ehs.web.model.FNPFeesService;
import ae.pcfc.etks.online.itworx.ehs.web.util.MarshallingUtility;
import ae.pcfc.etks.online.itworx.ehs.web.util.UtilCommon;

/**
 * Controller that handles the fnp interaction (listing services and calculating fees)
 * @author Ahmed.Mostafa
 *
 */
@Controller
@RequestMapping("/fnp")
@SessionAttributes("fnpAllServices")
public class FNPInteraction {

	private FNPDelegate fnpDelegate;
	private UserDelegate tscUserDelegate;
	private WorkflowDelegate workflowDelegate;
	
	private Logger log = LoggerFactory.getLogger(FNPInteraction.class);

	@Resource(name = "fnpDelegate")
	public void setFnpDelegate(FNPDelegate fnpDelegate) {
		this.fnpDelegate = fnpDelegate;
	}
	@Resource(name = "tscUserDelegate")
	public void setTscUserDelegate(UserDelegate tscUserDelegate) {
		this.tscUserDelegate = tscUserDelegate;
	}

	@Resource(name = "workflowDelegate")
	public void setWorkflowDelegate(WorkflowDelegate workflowDelegate) {
		this.workflowDelegate = workflowDelegate;
	}
	
	@RequestMapping(value = "/fnpMapping.htm", method = RequestMethod.GET)
	public String setupFNPForm(@ModelAttribute(value = "fnp") FNP fnp,Errors errors,Model model,HttpServletRequest request) {
		List<FNPFeesService> fnpAllServices=new ArrayList<FNPFeesService>();
		
		try {
			fnpAllServices = fnpDelegate.getServices();

			String userId=request.getParameter("userId");
			String requestId=request.getParameter("requestId");
			
			fnp.setUserId(userId);
			fnp.setRequestId(requestId);

		}catch (FNPServiceExpcetion fnpExp) {
			log.error("FNPInteraction.setupFNPForm() -  FNP error",fnpExp);
			ExceptionHandlerHelper.handleException(fnpExp);
			errors.reject(fnpExp.getErrorCode());
		} 
		catch (CommunicationException commExp) {
			log.error("FNPInteraction.setupFNPForm() -  communication error",commExp);
			ExceptionHandlerHelper.handleException(commExp);
			errors.reject(commExp.getErrorCode());
		}
		catch(Exception exp)
		{
			errors.reject("error.generic");
			log.error("FNPInteraction.setupFNPForm() -  error",exp);
		}
		
		model.addAttribute("fnp", fnp);
		model.addAttribute("fnpAllServices", fnpAllServices);
		
		return "fnp.fnpMapping";
	}

	@InitBinder
	protected void initBinder(HttpServletRequest request,
			ServletRequestDataBinder binder) throws Exception {
		binder.registerCustomEditor(List.class, "fnpServices",
				new CustomCollectionEditor(List.class) {
					protected Object convertElement(Object element) {
						if (element instanceof FNPFeesService) {
							return element;
						} else if (element instanceof String) {
							FNPFeesService service = new FNPFeesService();
							service.setServiceId(element.toString());
							return service;
						}
						return null;
					}
				});
	}
	
	//Query parameters for the services selected by the user (services Id are kepy in the FNP model)
	@RequestMapping(value = "/fnpMapping.htm", method = RequestMethod.POST)
	public String queryForServiceParametersAndMapped(Model model,
			@ModelAttribute(value = "fnp") FNP fnpModel,BindingResult errors,HttpServletRequest request) {
		try {
			FNP mappedParameters = unmarshallFNP();

			List<FNPFeesService> rawServices = null;

			if (null != fnpModel.getFnpServices()
					&& !fnpModel.getFnpServices().isEmpty()) {

				rawServices = fnpDelegate.getServiceInfoAndParameters(fnpModel
						.getServicesId());
			}
			OFCRequest ofcRequest = workflowDelegate.getDetails(Long.valueOf(fnpModel.getRequestId()), request.getUserPrincipal().getName(),true);
			
			
			FNP mergedFNP = mergeParametersList(rawServices, mappedParameters,ofcRequest);
			mergedFNP.setUserId(fnpModel.getUserId());
			mergedFNP.setRequestId(fnpModel.getRequestId());
			mergedFNP.setFormattedRequestId(UtilCommon.getFormmattedRequestId(ofcRequest.getService().getName(), ofcRequest.getType().name(), ofcRequest.getId(), ofcRequest.getCreationDate()));
			
			Calendar currentDate=Calendar.getInstance();
			currentDate.add(Calendar.MONTH, 6); //Add 6 months
			
			mergedFNP.setExpiryDate(currentDate.getTime());
			
			model.addAttribute("fnp", mergedFNP);
			
		} catch (CommunicationException commExp) {
			log.error("FNPInteraction.queryForServiceParametersAndMapped() -  communication error",commExp);
			ExceptionHandlerHelper.handleException(commExp);
			errors.reject(commExp.getErrorCode());
		} 
		catch (InvalidInputException invalInputExp) {
			log.error("FNPInteraction.queryForServiceParametersAndMapped() -  invalid input provided error",invalInputExp);
		}
		catch (FNPServiceExpcetion fnpExp) {
			log.error("FNPInteraction.setupFNPForm() -  FNP error",fnpExp);
			ExceptionHandlerHelper.handleException(fnpExp);
			errors.reject(fnpExp.getErrorCode());
		}		
		catch(Exception exp)
		{
			log.error("FNPInteraction.setupFNPForm() -  error",exp);
			errors.reject("error.generic");
		}
		return "fnp.fnpMapping";
	}

	//calculate FNP fees based on the supplied services and paramters
	@RequestMapping(value = "/calculate.htm", method = RequestMethod.POST)
	public String calculateFees(Model model,
			@ModelAttribute(value = "fnp") @Valid FNP fnpModel,BindingResult errors) {
		try {

			if(errors.hasErrors())
			{
				return "fnp.fnpMapping";
			}
			
			UserDetailVO userDetails =tscUserDelegate.findUserDetailByUserName(fnpModel.getUserId());
			Double fees = fnpDelegate.previewPaymentAdvice(fnpModel, userDetails);
			BigDecimal formattedFees = new BigDecimal(fees); 
			
			/*DecimalFormat df = new DecimalFormat();
		    df.setMinimumFractionDigits(3);
		    df.setMaximumFractionDigits(3);
		    df.setMinimumIntegerDigits(1);
		    df.setMaximumIntegerDigits(3);
		    df.setGroupingSize(20);
		    String formattedFeesString = df.format(formattedFees);
		    */

			StringWriter sw = new StringWriter(); // serialize
			ObjectMapper mapper = new ObjectMapper();
			MappingJsonFactory jsonFactory = new MappingJsonFactory();
			JsonGenerator jsonGenerator = jsonFactory.createJsonGenerator(sw);
			mapper.writeValue(jsonGenerator, fnpModel);
			sw.close();

			model.addAttribute("fnp", fnpModel);
			//model.addAttribute("fnpFees", new DecimalFormat(GlobalConstants.FEES_FORMAT).format(fees));
			//model.addAttribute("fnpFees", formattedFees);
			model.addAttribute("fnpFees", new DecimalFormat(GlobalConstants.FEES_FORMAT).format(formattedFees));
			model.addAttribute("fnpJSON", sw.toString());

		} catch (CommunicationException commExp) {
			log.error("FNPInteraction.calculateFees() - communication error ",commExp);
			ExceptionHandlerHelper.handleException(commExp);
			errors.reject(commExp.getErrorCode());
		} catch (JsonMappingException mapExp) {
			log.error("FNPInteraction.calculateFees() - JSON mapping error",mapExp);
			errors.reject("error.generic");
		} catch (JsonGenerationException genExp) {
			log.error("FNPInteraction.calculateFees() - JSON generation error ",genExp);
			errors.reject("error.generic");
		} catch (IOException genExp) {
			log.error("FNPInteraction.calculateFees() - IO error",genExp);
			errors.reject("error.generic");
		}
		catch (FNPServiceExpcetion fnpExp) {
			log.error("FNPInteraction.setupFNPForm() -  FNP error",fnpExp);
			ExceptionHandlerHelper.handleException(fnpExp);
			errors.reject(fnpExp.getErrorCode());
		}
		catch(Exception exp)
		{
			log.error("FNPInteraction.setupFNPForm() -  error",exp);
			errors.reject("error.generic");
		}
		return "fnp.fnpMapping";
	}

	private FNP mergeParametersList(List<FNPFeesService> rawServices,
			FNP unmarshalledFNP,OFCRequest ofcRequest) {
		Map<String, FNPFeesService> fnpServices = unmarshalledFNP.listToMap();
		FNP mergedFNP =new FNP();

		for (FNPFeesService rawService : rawServices) {
			if (fnpServices.containsKey(rawService.getServiceId())) {
				// this service was mapped, loop through its parameters and
				// check if it is mapped or no

				FNPFeesService fnpService = fnpServices.get(rawService
						.getServiceId());

				Map<String, FNPFeesParameter> parameterMap = fnpService
						.getParameterMap();

				for (FNPFeesParameter rowParameter : rawService
						.getFnpParameters()) {

					// check if the parameters that already mapped contains this
					// parameter or no
					if (!parameterMap.containsKey(rowParameter.getName())) {

						// new parameter was added to the service and will add
						// it to the parameter list for UI
						fnpService.getFnpParameters().add(rowParameter);
					}
					else
					{
						FNPFeesParameter parameter= null;
						try
						{
							//map the parameter value from the form mapping field (if the parameter was mapped by the admin before)
							parameter= parameterMap.get(rowParameter.getName());
							if(null!=parameter.getFormMappingField() && !parameter.getFormMappingField().isEmpty())
							{							
								Method getter=ofcRequest.getClass().getMethod("get"+parameter.getFormMappingField(),new Class[] {});
								Object value=(Object)getter.invoke(ofcRequest,new Object[] {});
							     if(null!=value)
							     {
							    	 parameter.setValue(value.toString());
							     }
							}
						}
						catch(NoSuchMethodException rfExp)
						{
							log.error("FNPInteraction.mergeParametersList() - no such method exist (get"+parameter.getFormMappingField()+") in OFCRequest class",rfExp);
						}
						catch(InvocationTargetException invExp)
						{
							log.error("FNPInteraction.mergeParametersList() - invocation target error",invExp);
						}
						catch(IllegalAccessException illExp)
						{
							log.error("FNPInteraction.mergeParametersList() - illegal access error",illExp);
						}
					}
				}
				
				mergedFNP.getFnpServices().add(fnpService);
			} else {
				// this service wasn't mapped, loop through its parameters and
				// add it to the paramters to be displayed
				FNPFeesService fnpService = new FNPFeesService();
				fnpService.setServiceId(rawService.getServiceId());
				fnpService.getFnpParameters().addAll(
						rawService.getFnpParameters());

				mergedFNP.getFnpServices().add(fnpService);
			}
		}
		return mergedFNP;
	}

	/**
	 * unmarshall the FNP model
	 * @return unmarshalled FNP model
	 */
	private FNP unmarshallFNP() {
		FNP fnp = null;
		try {
			fnp = MarshallingUtility.unmarshallFNP();
		} catch (Exception exp) {
			log.error("FNPInteraction.unmarshallFNP() - error while unmarshalling FNP",exp);
		}
		if (null == fnp) {
			fnp = new FNP();
		}
		return fnp;
	}
}